#include "game_layer.hpp"
#include "ode/objects.h"
#include "xen/key_codes.hpp"
#include "xen/physical/geom.hpp"

GameLayer::GameLayer()
:Xen::Layer("Game Layer"),
dirlight_({{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}, {0, -1, -1}),
dotlight_({{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, {1, 0.1, 0.08}, {0, 0, 0}),
spotlight_({{0.4, 0.4, 0.4}, {0.7, 0.7, 0.7}, {0.5, 0.5, 0.5}}, {20, 30}, {0, 0, 0}, {0, 0, -19.5})
{
    canva_size = glm::vec2(Application::Get().GetWindow().GetSize().w, Application::Get().GetWindow().GetSize().h);
    initCameras();
    initModels();
    initPhysical();
    load2DResources();

    welcome_scene_ = std::make_unique<WelcomeScene>(this);
    scene_ = welcome_scene_.get();
}

GameLayer::~GameLayer() {

}

void GameLayer::OnUpdate(TimeStep ts) {
    scene_->OnUpdate(ts);
}

void GameLayer::OnEvent(Event& e) {
    scene_->OnEvent(e);
}

void GameLayer::initCameras() {
    controller_ = std::make_unique<Xen::PerspectiveCameraController>(
            45,
            static_cast<float>(canva_size.x)/canva_size.y,
            0.01, 100
            );
  
    ortho_camera_ = std::make_shared<OrthoCamera>(0, canva_size.x, canva_size.y, 0);
}

void GameLayer::initPhysical() {
    world_.SetGravity(glm::vec3(0, -9.8, 0));
}

void GameLayer::initModels() {
    basketball_ = ModelLoader::Create(texture2d_lib_, "assets/Basketball/Bball.dae");
    ok_target_ = ModelLoader::Create(texture2d_lib_, "assets/OKTarget/mis_obj_shooting_SD.obj");
    fail_target_ = ModelLoader::Create(texture2d_lib_, "assets/OKTarget/mis_obj_shooting_SD.obj");
    brick_wall_ = ModelLoader::Create(texture2d_lib_, "assets/brick/brick.obj");
}

void GameLayer::load2DResources() {
    texture2d_lib_.Load("start label", "assets/start.png");
    texture2d_lib_.Load("title", "assets/title.png");
    texture2d_lib_.Load("win", "assets/win.png");
    texture2d_lib_.Load("failed", "assets/failed.png");
    texture2d_lib_.Load("strength_bar", "assets/strength_bar.png");
    texture2d_lib_.Load("left_pointer", "assets/left_pointer.png");
}


/***********************
 * WelcomeScene
 ***********************/

GameLayer::WelcomeScene::WelcomeScene(GameLayer* layer):game_layer_(layer) {}

void GameLayer::WelcomeScene::OnUpdate(TimeStep& ts) {
    RenderCommand::SetClearColor(glm::vec4(0, 0, 0, 1));
    RenderCommand::Clear();

    switch (mode_) {
        case GameMode::Welcome:
            updateWelcome(ts);
            break;
        case GameMode::Animation:
            updateAnimation(ts);
            break;
        case GameMode::Game:
            updateGame(ts);
            break;
    }
}

void GameLayer::WelcomeScene::updateWelcome(TimeStep& ts) {
    Renderer3D::BeginScene(game_layer_->controller_->GetCamera(), game_layer_->dirlight_, game_layer_->dotlight_, game_layer_->spotlight_);
    {
        Renderer3D::DrawModel(game_layer_->basketball_, {-6, -3, -20});
        Renderer3D::DrawModel(game_layer_->basketball_, {-4.5, -3, -20});
        Renderer3D::DrawModel(game_layer_->basketball_, {-5.25, -1.7, -20});
        Renderer3D::DrawModel(game_layer_->ok_target_, {0, 0, -15}, {0, 0, 0}, {0.2, 0.2, 0.2});
        Renderer3D::DrawModel(game_layer_->brick_wall_, {0, 0, -45}, {90, 0, 0}, {30, 30, 1});
    }
    Renderer3D::EndScene();

    Renderer2D::BeginScene(*game_layer_->ortho_camera_);
    {
        auto title_label = game_layer_->texture2d_lib_.Get("title");
        glm::vec2 title_label_size(title_label->GetWidth(), title_label->GetHeight());
        title_label_size *= 0.5;
        Renderer2D::DrawTexture({game_layer_->canva_size.x/2, 600}, title_label_size, title_label);
        auto start_label = game_layer_->texture2d_lib_.Get("start label");
        glm::vec2 start_label_size(start_label->GetWidth(), start_label->GetHeight());
        start_label_size *= 0.5;
        Renderer2D::DrawTexture({game_layer_->canva_size.x/2, start_label_y_}, start_label_size, start_label);
    }
    Renderer2D::EndScene();

    if (start_label_y_increase_) {
        start_label_y_ += 2;
    } else {
        start_label_y_ -= 2;
    }
    if (start_label_y_ < 80) {
        start_label_y_ = 80;
        start_label_y_increase_ = true;
    }
    if (start_label_y_ > 120) {
        start_label_y_ = 120;
        start_label_y_increase_ = false;
    }

    if (Input::IsKeyPressed(Xen::XEN_SPACE)) {
        mode_ = GameMode::Animation;
    }
}

void GameLayer::WelcomeScene::eventWelcome(Event& e) {
}

void GameLayer::WelcomeScene::updateAnimation(TimeStep& ts) {
    static glm::vec3 ball1_init_pos = {-6, -3, -20};
    static glm::vec3 ball1_pos = ball1_init_pos;

    static int life = 3;

    Renderer3D::BeginScene(game_layer_->controller_->GetCamera(), game_layer_->dirlight_, game_layer_->dotlight_, game_layer_->spotlight_);
    {
        Renderer3D::DrawModel(game_layer_->basketball_, ball1_pos);
        Renderer3D::DrawModel(game_layer_->ok_target_, {0, 0, -15}, {0, 0, 0}, {0.2, 0.2, 0.2});
        Renderer3D::DrawModel(game_layer_->brick_wall_, {0, 0, -45}, {90, 0, 0}, {30, 30, 1});
    }
    Renderer3D::EndScene();

    static uint32_t frame = 0;
    if (frame < 50) {
        frame++;
        game_layer_->spotlight_.position.z += 0.4;
    }
    if (frame >= 50 && frame <= 70) {
        auto move_dir = (glm::vec3(0, -1.2, -2) - ball1_init_pos);
        ball1_pos.x += move_dir.x / 20.0;
        ball1_pos.y += move_dir.y / 20.0;
        ball1_pos.z += move_dir.z / 20.0;
        frame++;
    }
    if (frame >= 70) {
        mode_ = GameMode::Game;
    }
}

void GameLayer::WelcomeScene::eventAnimation(Event& e) {
}

struct Data {
    dWorldID world;
    dJointGroupID joint_group;
    Physical::PlaneGeomentry* plane;
    Physical::PlaneGeomentry* target;
    bool* can_operate;
};

static void nearCallback(void *data, dGeomID o1, dGeomID o2) {
    const int N = 10;
    dContact contact[N];
    Data* d = (Data*)data;
    int n = dCollide(o1, o2, N, &contact[0].geom, sizeof(contact));
    for (int i = 0; i < n; i++) {
        contact[i].surface.mode = dContactBounce;
        contact[i].surface.mu = dInfinity;
        contact[i].surface.bounce = 0.8;
        contact[i].surface.bounce_vel = 0;
        dJointID joint = dJointCreateContact(d->world, d->joint_group, &contact[i]);
        dJointAttach(joint, dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
    }
}

enum class GameStatus {
    Gaming,
    Fail,
    Win,
};

void GameLayer::WelcomeScene::updateGame(TimeStep& ts) {
    static Ref<Physical::SphereBody> body = game_layer_->world_.CreateSphere(1.0, {0, -1.2, -2}, 1.5);
    static Ref<Physical::SphereGeomentry> geom = game_layer_->space_.CreateShpere(1.5);
    body->AttachGeom(geom);

    static Ref<Physical::PlaneGeomentry> target_geom = game_layer_->space_.CreatePlane({0, 0, 1, -14});
    static Ref<Physical::PlaneGeomentry> bottom_plane_geom = game_layer_->space_.CreatePlane({0, 1, 0, -2.5});

    static GameStatus game_over = GameStatus::Gaming;

    Renderer3D::BeginScene(game_layer_->controller_->GetCamera(), game_layer_->dirlight_, game_layer_->dotlight_, game_layer_->spotlight_);
    {
        if (game_over == GameStatus::Gaming) {
            Renderer3D::DrawModel(game_layer_->basketball_, body->GetPosition(), body->GetRotation());
        }
        Renderer3D::DrawModel(game_layer_->ok_target_, {0, 0, -15}, {0, 0, 0}, {0.2, 0.2, 0.2});
        Renderer3D::DrawModel(game_layer_->brick_wall_, {0, 0, -45}, {90, 0, 0}, {30, 30, 1});
    }
    Renderer3D::EndScene();

    Renderer2D::BeginScene(*game_layer_->ortho_camera_);
        if (game_over == GameStatus::Fail) {
            auto fail_label = game_layer_->texture2d_lib_.Get("failed");
            if (fail_label) {
                glm::vec2 fail_size = {fail_label->GetWidth(), fail_label->GetHeight()};
                Renderer2D::DrawTexture({game_layer_->canva_size.x/2, game_layer_->canva_size.y/2}, fail_size, fail_label);
            }
        } else if (game_over == GameStatus::Win) {
            auto win_label = game_layer_->texture2d_lib_.Get("win");
            if (win_label) {
                glm::vec2 win_size = {win_label->GetWidth(), win_label->GetHeight()};
                Renderer2D::DrawTexture({game_layer_->canva_size.x/2, game_layer_->canva_size.y/2}, win_size, win_label);
            }
        }
    Renderer2D::EndScene();

    static bool can_operate = true;

    static const int low_strength = 100;
    static const int high_strength = 500;
    static int strength = low_strength;

    if (can_operate) {
        if (Input::IsKeyPressed(Xen::XEN_SPACE)) {
            can_operate = false;
            body->SetForce(glm::vec3(0, strength, -strength));
        }
    } else {
        auto pos = body->GetPosition();
        if (game_over == GameStatus::Gaming) {
            if (pos.y <= -2.5 + 1.5) {
                game_over = GameStatus::Fail;
            }
            if (pos.z <= -14 + 1.5) {
                auto ball_pos = body->GetPosition();
                if (sqrt(ball_pos.x*ball_pos.x + ball_pos.y*ball_pos.y) <= 2) {
                    game_over = GameStatus::Win;
                } else {
                    game_over = GameStatus::Fail;
                }
            }
        }
    }

    static int increase = 5;

    if (can_operate) {
        auto strength_bar = game_layer_->texture2d_lib_.Get("strength_bar");
        auto left_point = game_layer_->texture2d_lib_.Get("left_pointer");

        if (strength < low_strength) {
            increase = 5;
        }
        if (strength > high_strength) {
            increase = -5;
        }
        strength += increase;

        if (strength_bar) {
            glm::vec2 point_size = {left_point->GetWidth(), left_point->GetHeight()};
        }

        glm::vec2 point_size = {left_point->GetWidth(), left_point->GetHeight()};
        point_size *= 0.2;

        Renderer2D::BeginScene(*game_layer_->ortho_camera_);
        {
            Renderer2D::DrawTexture({100, 200}, {30, 150}, strength_bar);
            Renderer2D::DrawTexture({130, 90+ 150 * (1.0f * strength)/(high_strength - low_strength)}, point_size, left_point);
        }
        Renderer2D::EndScene();
    }

    static dJointGroupID joint_group = dJointGroupCreate(0);

    Data data = {game_layer_->world_.GetRaw(), joint_group, bottom_plane_geom.get(), target_geom.get(), &can_operate};
    game_layer_->space_.Update(nearCallback, (void*)&data);
    game_layer_->world_.Update(ts);
    dJointGroupEmpty(joint_group);
}

void GameLayer::WelcomeScene::eventGame(Event& e) {
}

void GameLayer::WelcomeScene::OnEvent(Event& e) {
    switch (mode_) {
        case GameMode::Welcome:
            eventWelcome(e);
            break;
        case GameMode::Animation:
            eventAnimation(e);
            break;
        case GameMode::Game:
            eventGame(e);
            break;
    }
}
